﻿using System;
using System.Data;
using System.Linq;
using MbUnit.Framework;
using Mite.Configuration;
using Mite.DSL;
using Mite.MsSql;
using Rhino.DSL;

namespace Mite.Core.IntegrationTests.MsSql
{
	[TestFixture]
	[Author("Nathan")]
	[TestCategory("Integration")]
	public class MigrationTestFixture
	{
		[SetUp]
		public void Setup()
		{
			oldCurrentDirectory = Environment.CurrentDirectory;
			Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
			Settings.Load("MsSql\\settings.boo");
			MsSqlUtility.CleanDatabase(GetConnection(), GetEnvironment().Database);
		}

		[TearDown]
		public void Teardown()
		{
			Environment.CurrentDirectory = oldCurrentDirectory;
		}

		[Test]
		public void CreateTable_runs()
		{
			MigrationBase migration = LoadMigration(createAndDropTableFixtureUrl);
			migration.Run(MigrationDirection.Up);
		}

		[Test]
		public void CreateTable_migrate_up_then_down()
		{
			MigrationBase migration = LoadMigration(createAndDropTableFixtureUrl);
			migration.Run(MigrationDirection.Up);
			migration.Run(MigrationDirection.Down);
		}

		[Test]
		public void Migrating_down_to_zero_should_not_delete_schema_table()
		{
			MigrationBase migration = LoadMigration(createAndDropTableFixtureUrl);
			migration.Run(MigrationDirection.Up);
			migration.Run(MigrationDirection.Down);

			AssertTableExists(Adapter.MIGRATION_SCHEMA_TABLE_NAME);
		}

		[Test]
		public void Migrating_up_from_zero_when_schema_table_already_exists_should_not_recreate_table()
		{
			MigrationBase migration = LoadMigration(createAndDropTableFixtureUrl);
			migration.Run(MigrationDirection.Up);
			migration.Run(MigrationDirection.Down);
			migration.Run(MigrationDirection.Up);

			AssertTableExists("Animals");
			AssertTableExists(Adapter.MIGRATION_SCHEMA_TABLE_NAME);
		}

		[Test]
		[ExpectedException(typeof (MigrationVersionException))]
		public void Migrating_down_from_zero_throws()
		{
			MigrationBase migration = LoadMigration(createAndDropTableFixtureUrl);
			migration.Run(MigrationDirection.Down);
		}

		[Test]
		public void Rename_table_changes_name_of_table()
		{
			MigrationBase createFixture = LoadMigration(createAndDropTableFixtureUrl);
			createFixture.Run(MigrationDirection.Up);

			MigrationBase renameFixture = LoadMigration(renameTableFixtureUrl);
			renameFixture.Run(MigrationDirection.Up);

			AssertTableExists("Beasts");
		}

		[Test]
		public void Add_column_to_table()
		{
			MigrationBase createFixture = LoadMigration(createAndDropTableFixtureUrl);
			createFixture.Run(MigrationDirection.Up);

			MigrationBase addColumnFixture = LoadMigration(addColumnFixtureUrl);
			addColumnFixture.Run(MigrationDirection.Up);

			Table table = GetTable("Animals");
			Assert.IsNotNull(table.Columns.SingleOrDefault(col => col.Name == "Age"));
		}

		[Test]
		public void Remove_column_from_table()
		{
			var tableFixture = new Table("test_table", GetIdColumn(),
			                             new MigrationBase.Boolean("IsTrue"));

			IAdapter adapter = GetAdapter();
			adapter.CreateTable(GetConnection(), tableFixture).ExecuteNonQuery();

			MigrationBase fixture = LoadMigration(removeColumnFromTableFixtureUrl);
			fixture.Run(MigrationDirection.Up);

			Table tbl = GetTable("test_table");
			Assert.IsNull(tbl.Columns.SingleOrDefault(col => col.Name.Equals("IsTrue")));
		}

		[Test]
		public void Version_matches_filename_standard()
		{
			MigrationBase createFixture = LoadMigration(createAndDropTableFixtureUrl);
			Assert.AreEqual(1, createFixture.Version);
		}

		[Test]
		public void Change_column_datatype_from_string_to_int()
		{
			var tableFixture = new Table("test_table", new MigrationBase.String("Age")
			                                           {
			                                           	Length = 55
			                                           });

			IAdapter adapter = GetAdapter();
			adapter.CreateTable(GetConnection(), tableFixture).ExecuteNonQuery();

			MigrationBase fixture = LoadMigration(changeColumnFromStringToIntUrl);
			fixture.Run(MigrationDirection.Up);

			Table tbl = GetTable("test_table");
			Column ageColumn = tbl.Columns.SingleOrDefault(col => col.Name.Equals("Age"));

			Assert.AreEqual(DbType.Int32, ageColumn.DataType);
		}

		[Test]
		public void Parent_Child_Test()
		{
			IAdapter adapter = GetAdapter();

			MigrationBase fixture = LoadMigration("MsSql\\DSL\\parent_child_001.boo");
			fixture.Run(MigrationDirection.Up);

			AssertColumnMatchesExpectedParameters("Parent", "Id", DbType.Int32,
			                                      ColumnAttributes.Identity | ColumnAttributes.PrimaryKey);
			AssertColumnMatchesExpectedParameters("Child", "Id", DbType.Int32,
															  ColumnAttributes.Identity | ColumnAttributes.PrimaryKey);
			AssertColumnMatchesExpectedParameters("Parent", "Name", DbType.String,
												  ColumnAttributes.None);
			AssertColumnMatchesExpectedParameters("Child", "Name", DbType.String,
												  ColumnAttributes.NotNull);
			AssertColumnMatchesExpectedParameters("Child", "ParentId", DbType.Int32,
												  ColumnAttributes.NotNull);

		}

		private void AssertColumnMatchesExpectedParameters(string table, string column, DbType dataType, ColumnAttributes attributes)
		{
			Table tbl = GetTable(table);
			Assert.IsNotNull(tbl);

			Column col = tbl.Columns.SingleOrDefault(c => c.Name.Equals(column));
			Assert.IsNotNull(col, "Expected {0] to have column named {1}.", table, column);

			Assert.AreEqual(dataType, col.DataType, "Expected {0} to have data type {1}", column, dataType);
			Assert.AreEqual(attributes, col.Attributes, "Expected {0} to have attributes {1} but found {2}.", column, attributes, col.Attributes);
		}

		private static MiteEnvironment GetEnvironment()
		{
			return Settings.GetEnvironment("Test");
		}

		private MigrationBase LoadMigration(string url)
		{
			var factory = new DslFactory();
			factory.Register<MigrationBase>(new MigrationDslEngine());
			var migration = factory.Create<MigrationBase>(url, GetEnvironment());
			return migration;
		}

		protected virtual IDbConnection GetConnection()
		{
			MiteEnvironment env = GetEnvironment();

			IAdapter adapter = GetAdapter();
			IDbConnection conn = adapter.CreateConnection(env.Host, env.Database, env.Username, env.Password, env.Options);
			conn.Open();

			return conn;
		}

		protected virtual IAdapter GetAdapter()
		{
			IAdapter adapter = IoC.Resolve<IAdapterFactory>().Create(GetEnvironment());
			return adapter;
		}

		private void AssertTableExists(string tableName)
		{
			IDbConnection conn = GetConnection();

			Assert.IsNotNull(GetTable(tableName), string.Format("{0} not found.", tableName));
		}

		private Database GetDatabase()
		{
			return GetAdapter().GetDatabase(GetConnection(), GetEnvironment().Database);
		}

		private Table GetTable(string table)
		{
			return GetDatabase().Tables.SingleOrDefault(tbl => tbl.Name.Equals(table));
		}

		private static Column GetIdColumn()
		{
			return new Column("Id", DbType.Int32, ColumnAttributes.PrimaryKey | ColumnAttributes.Identity);
		}

		private const string addColumnFixtureUrl = "MsSql\\DSL\\animals_add_column_002.boo";
		private const string changeColumnFromStringToIntUrl = "MsSql\\DSL\\change_column_from_string_to_int_001.boo";
		private const string createAndDropTableFixtureUrl = "MsSql\\DSL\\create_table_001.boo";
		private const string removeColumnFromTableFixtureUrl = "MsSql\\DSL\\fixture_remove_column_from_table_001.boo";
		private const string renameTableFixtureUrl = "MsSql\\DSL\\rename_table_002.boo";
		private string oldCurrentDirectory;
	}
}